package com.eunion.core.common.jpa.deletgate;

import java.io.Serializable;
import java.util.List;

import javax.persistence.EntityManager;

import org.springframework.data.jpa.repository.support.JpaRepositoryFactory;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;

import com.eunion.core.common.jpa.ProjectionQueryExecutor;
import com.eunion.core.common.jpa.impl.SimpleProjectionQueryExecutorImpl;

public class ProjectionSupportedJpaRepositoryFactorySupport extends JpaRepositoryFactory {

    public ProjectionSupportedJpaRepositoryFactorySupport(EntityManager entityManager) {
        super(entityManager);
    }

    @Override
    protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
        return SimpleProjectionQueryExecutorImpl.class;
    }

    @Override
    protected RepositoryMetadata getRepositoryMetadata(Class<?> repositoryInterface) {
        ProjectionRepositoryMetadata metadata = new ProjectionRepositoryMetadata(super.getRepositoryMetadata(repositoryInterface));
        if (ProjectionQueryExecutor.class.isAssignableFrom(repositoryInterface)) {
            metadata.setBeanClass(resolveBeanType(repositoryInterface));
        }
        return metadata;
    }

    private Class<?> resolveBeanType(Class<?> repositoryInterface) {
        TypeInformation<?> information = ClassTypeInformation.from(repositoryInterface);
        List<TypeInformation<?>> arguments = information.getSuperTypeInformation(ProjectionQueryExecutor.class)
                .getTypeArguments();
        if (arguments.size() < 2 || arguments.get(1) == null) {
            throw new IllegalArgumentException(String.format("Could not resolve id type of %s!", repositoryInterface));
        }
        return arguments.get(1).getType();
    }

    @Override
    protected RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata,
            Class<?> customImplementationClass) {
        ProjectionRepositoryInformation repositoryInformation = new ProjectionRepositoryInformation(
                super.getRepositoryInformation(metadata, customImplementationClass));
        if (metadata instanceof ProjectionRepositoryMetadata) {
            repositoryInformation.setBeanClass(((ProjectionRepositoryMetadata) metadata).getBeanClass());
        }
        return repositoryInformation;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Override
    protected <T, ID extends Serializable> SimpleJpaRepository<?, ?> getTargetRepository(
            RepositoryInformation information, EntityManager entityManager) {
        ProjectionJpaEntityInformation<T, ?, Serializable> entityInformation = new ProjectionJpaEntityInformation(
                getEntityInformation(information.getDomainType()));
        if (information instanceof ProjectionRepositoryInformation) {
            Class beanClass = ((ProjectionRepositoryInformation) information).getBeanClass();
            entityInformation.setBeanClass(beanClass);
        }
        return getTargetRepositoryViaReflection(information, entityInformation, entityManager);
    }

}
